﻿using System;
using System.Collections;
using System.Collections.Generic;
using LibNoise;
using LibNoise.Generator;
using UnityEngine;
using Zzzxl.Util;

[RequireComponent (typeof (MeshFilter))]
[RequireComponent (typeof (MeshRenderer))]
[RequireComponent (typeof (MeshCollider))]
public class Chunk : MonoBehaviour {
    public static int width = 16;
    public static int height = 16;

    public byte[, , ] blocks;
    public Vector3i position;

    private Mesh mesh;
    private List<Vector3> vertices = new List<Vector3> ();
    private List<int> triangles = new List<int> ();
    private List<Vector2> uv = new List<Vector2> ();
    private static float offset = 1 / 32f;
    private static float shrink = .001f;
    private bool isWorking = false;
    private static Vector3i originPosition = new Vector3i (0, 0, 0);

    // Use this for initialization
    void Start () {
        position = new Vector3i (this.transform.position);
        if (Map.instance.chunks.ContainsKey (position)) {
            Destroy (this);
        } else {
            Map.instance.chunks.Add (position, this.gameObject);
            mesh = new Mesh ();
            mesh.name = "Chunk";
            this.name = string.Format ("chunk({0}, {1}, {2})", position.x, position.y, position.z);
            StartCoroutine (CreateMap ());
        }
    }

    private IEnumerator CreateMap () {
        while (isWorking) {
            yield return null;
        }
        isWorking = true;
        blocks = new byte[Chunk.width, Chunk.height, Chunk.width];

        int seed = GameManager.seed;
        Perlin perlin = new LibNoise.Generator.Perlin (1f, 1f, 1f, 8, seed, QualityMode.High);

        for (int x = 0; x < Chunk.width; x++) {
            for (int y = 0; y < Chunk.height; y++) {
                for (int z = 0; z < Chunk.height; z++) {
                    byte blockid = Terrain.GetTerrainBlock (new Vector3i (x, y, z) + position, perlin, seed);
                    if (blockid == 1 && Terrain.GetTerrainBlock (new Vector3i (x, y + 1, z) + position, perlin, seed) == 0) {
                        blocks[x, y, z] = 2;
                    } else {
                        blocks[x, y, z] = Terrain.GetTerrainBlock (new Vector3i (x, y, z) + position, perlin, seed);
                    }
                }
            }
        }
        StartCoroutine (CreateMesh ());
    }

    private IEnumerator CreateMesh () {
        vertices.Clear ();
        triangles.Clear ();
        uv.Clear ();

        for (int x = 0; x < Chunk.width; x++) {
            for (int y = 0; y < Chunk.height; y++) {
                for (int z = 0; z < Chunk.width; z++) {
                    Block block = BlockList.GetBlock (this.blocks[x, y, z]);
                    if (block == null) continue;
                    //front and back
                    if (IsPlaneTransparent (x + 1, y, z)) AddFrontFace (x, y, z, block);
                    if (IsPlaneTransparent (x - 1, y, z)) AddBackFace (x, y, z, block);

                    //left and right
                    if (IsPlaneTransparent (x, y, z - 1)) AddLeftFace (x, y, z, block);
                    if (IsPlaneTransparent (x, y, z + 1)) AddRightFace (x, y, z, block);

                    //up and bottom
                    if (IsPlaneTransparent (x, y + 1, z)) AddUpFace (x, y, z, block);
                    if (IsPlaneTransparent (x, y - 1, z)) AddBottomFace (x, y, z, block);
                }
            }
        }

        mesh.vertices = vertices.ToArray ();
        mesh.triangles = triangles.ToArray ();
        mesh.uv = uv.ToArray ();

        mesh.RecalculateBounds ();
        mesh.RecalculateNormals ();

        GetComponent<MeshFilter> ().mesh = mesh;
        GetComponent<MeshCollider> ().sharedMesh = mesh;

        yield return null;
        isWorking = false;
    }

    /*在平面的法线的反方向上向该面看去，平面不透明，反之透明
                               |
                  normal line  |
    <)--->      <--------------|     <---(> 视线从此看去，是透明的
                               |
                               |
    */
    public bool IsPlaneTransparent (int x, int y, int z) {
        if (x >= originPosition.x + width || y >= originPosition.y + height || z >= originPosition.z + width ||
            x < 0 || y < 0 || z < 0) {
            return true;
        } else {
            return this.blocks[x, y, z] == 0;
        }
    }

    private void AddFrontFace (int xCoord, int yCoord, int zCoord, Block block) {
        setTri (this.triangles, vertices.Count);

        vertices.Add (new Vector3 (xCoord, yCoord, zCoord));
        vertices.Add (new Vector3 (xCoord, yCoord, zCoord + 1));
        vertices.Add (new Vector3 (xCoord, yCoord + 1, zCoord + 1));
        vertices.Add (new Vector3 (xCoord, yCoord + 1, zCoord));

        AddToUV (block.textureFrontX, block.textureFrontY);
    }

    private void AddBackFace (int xCoord, int yCoord, int zCoord, Block block) {
        setTri (this.triangles, vertices.Count);

        vertices.Add (new Vector3 (xCoord - 1, yCoord, zCoord + 1));
        vertices.Add (new Vector3 (xCoord - 1, yCoord, zCoord));
        vertices.Add (new Vector3 (xCoord - 1, yCoord + 1, zCoord));
        vertices.Add (new Vector3 (xCoord - 1, yCoord + 1, zCoord + 1));

        AddToUV (block.textureBackX, block.textureBackY);
    }

    private void AddLeftFace (int xCoord, int yCoord, int zCoord, Block block) {
        setTri (this.triangles, vertices.Count);

        vertices.Add (new Vector3 (xCoord - 1, yCoord, zCoord));
        vertices.Add (new Vector3 (xCoord, yCoord, zCoord));
        vertices.Add (new Vector3 (xCoord, yCoord + 1, zCoord));
        vertices.Add (new Vector3 (xCoord - 1, yCoord + 1, zCoord));

        AddToUV (block.textureLeftX, block.textureLeftY);
    }

    private void AddRightFace (int xCoord, int yCoord, int zCoord, Block block) {
        setTri (this.triangles, vertices.Count);

        vertices.Add (new Vector3 (xCoord, yCoord, zCoord + 1));
        vertices.Add (new Vector3 (xCoord - 1, yCoord, zCoord + 1));
        vertices.Add (new Vector3 (xCoord - 1, yCoord + 1, zCoord + 1));
        vertices.Add (new Vector3 (xCoord, yCoord + 1, zCoord + 1));

        AddToUV (block.textureRightX, block.textureRightY);
    }

    private void AddUpFace (int xCoord, int yCoord, int zCoord, Block block) {
        setTri (this.triangles, vertices.Count);

        vertices.Add (new Vector3 (xCoord, yCoord + 1, zCoord));
        vertices.Add (new Vector3 (xCoord, yCoord + 1, zCoord + 1));
        vertices.Add (new Vector3 (xCoord - 1, yCoord + 1, zCoord + 1));
        vertices.Add (new Vector3 (xCoord - 1, yCoord + 1, zCoord));

        AddToUV (block.textureUpX, block.textureUpY);
    }

    private void AddBottomFace (int xCoord, int yCoord, int zCoord, Block block) {
        setTri (this.triangles, vertices.Count);

        vertices.Add (new Vector3 (xCoord, yCoord, zCoord + 1));
        vertices.Add (new Vector3 (xCoord, yCoord, zCoord + 0));
        vertices.Add (new Vector3 (xCoord - 1, yCoord, zCoord));
        vertices.Add (new Vector3 (xCoord - 1, yCoord, zCoord + 1));

        AddToUV (block.textureBottomX, block.textureBottomY);
    }

    private void setTri (List<int> tri, int index) {
        //第一个三角形
        tri.Add (0 + index);
        tri.Add (3 + index);
        tri.Add (2 + index);
        //第二个三角形
        tri.Add (2 + index);
        tri.Add (1 + index);
        tri.Add (0 + index);
    }

    private void AddToUV (byte lp, byte rp) {
        this.uv.Add (new Vector2 (lp * offset, rp * offset) + new Vector2 (shrink, shrink));
        this.uv.Add (new Vector2 (lp * offset + offset, rp * offset) + new Vector2 (-shrink, shrink));
        this.uv.Add (new Vector2 (lp * offset + offset, rp * offset + offset) + new Vector2 (-shrink, -shrink));
        this.uv.Add (new Vector2 (lp * offset, rp * offset + offset) + new Vector2 (shrink, -shrink));
    }
}